/*
 * Alloy Analyzer
 * Copyright (c) 2002 Massachusetts Institute of Technology
 *
 * The Alloy Analyzer is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * The Alloy Analyzer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Alloy Analyzer; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package alloy.semantic;

import java.util.ArrayList;
import java.util.List;

import alloy.ast.ASTDepthFirstReturnVisitor;
import alloy.ast.ArgList;
import alloy.ast.Decl;
import alloy.ast.Function;
import alloy.ast.InvocablePara;
import alloy.ast.Module;
import alloy.ast.ModuleInfo;
import alloy.ast.MultiplicityExpr;
import alloy.ast.Predicate;
import alloy.ast.Variable;
import alloy.semantic.ExprTypeCheckVisitor.TypeCheckException;
import alloy.type.UnionType;
import alloy.util.Dbg;
import alloy.util.Msg;

/**
 * <p>
 * Alloy Analyzer<br>
 * Copyright (c) 2001<br>
 * Massachusetts Institute of Technology<br>
 * <p>
 * Class: FunctionArgTypeCheckVisitor<br>
 * owner: msridhar<br>
 * Interface status: unstable<br>
 * Implementation status: unstable<br>
 * <p>
 * A visitor which types checks the formal arguments of
 * all invocables in the spec.
 */

public class FunctionArgTypeCheckVisitor extends ASTDepthFirstReturnVisitor {

    /**
     * Visit a function.
     * @param function_ the function whose arguments are to be typechecked
     * @return <code>function_</code> if all arguments properly typecheck;<code>null</code> otherwise
     */
    public Object visit(Function function_) {
        // add all the bindings for the formals
        visitInvocable(function_);

        // add the binding for the return formal
        ModuleInfo moduleInfo = ((Module)function_.getParent().getParent()).getModuleInfo();
        LocalScope functionLocalScope = function_.getLocalScope();
        Decl returnDecl = function_.getReturnDecl();
        Variable returnVar = (Variable)returnDecl.getVariables().childAt(0);
        UnionType returnType = addBindingToLocalScope(returnVar, function_.getReturnDecl(),
                                                      functionLocalScope, moduleInfo);
        return returnType == null ? null : function_;

    }

    /**
     * Visit a predicate.
     * @param predicate_ the predicate whose arguments are to be typechecked
     * @return <code>predicate_</code> if all arguments properly typecheck;<code>null</code> otherwise
     */
    public Object visit(Predicate predicate_) {
        return visitInvocable(predicate_);
    }

    private Object visitInvocable(InvocablePara invocable_) {
        // get module scope for this function
        ModuleInfo moduleInfo = ((Module)invocable_.getParent().getParent()).getModuleInfo();
        LocalScope invocableLocalScope = invocable_.getLocalScope();

        // going to create type for each argument
        List argTypes = new ArrayList(); // Type
        ArgList argList = invocable_.getArgList();
        ArgList.InOrderFormalIter declIter = argList.orderedIter();

        // now, do main arg decls
        while (declIter.hasNext()) {
            Variable var = declIter.nextVar();
            Decl decl = declIter.getCurDecl();
            UnionType declType = addBindingToLocalScope(var, decl, invocableLocalScope, moduleInfo);
            if (declType == null) return null;
            else argTypes.add(declType);
        }

        // if we reached this point, must have typed all arguments properly
        // set the function's argument types list, and return the function
        argList.setArgTypes(argTypes);
        return invocable_;
    }

    /**
     * Add a binding for a variable of a decl to a local scope.  Type
     * check the expression of the declaration, and bind the
     * variable to that type in the local
     * scope.  Ensure that there are no invocations in the expression.
     * @param var_ the variable
     * @param decl_ the declaration to be checked
     * @param localScope_ the local scope to which the decl. bindings should be added
     * @param moduleScope_ the module scope for the enclosing module
     * @return the type of <code>decl</code> if it typechecks and gets added properly; <code>null</code> otherwise
     */
    private UnionType addBindingToLocalScope(Variable var_, Decl decl_, LocalScope localScope_, ModuleInfo moduleScope_) {
        // make sure expression of decl. has no invocations
        MultiplicityExpr declExpr = decl_.getExpr();
        UnionType declType = declExpr.getType();
        if (declType == null) {
            Object invRet = declExpr.applyReturnVisitor(new InvocationErrorVisitor());
            if (invRet == null) return null;
            // type check expression
            try {
                decl_.setExpr((MultiplicityExpr)ExprTypeCheckVisitor.typecheckNode(decl_.getExpr(),
										   moduleScope_, localScope_));
            }
            catch (TypeCheckException e) { return null; }
            declType = decl_.getExpr().getType();
        }

        try {
            localScope_.addMapping(var_.getId().nodeString(), declType);
            //Dbg.info("type for " + var.getId().orignodeString() + ": " + declType.toString());
        } catch (AlreadyMappedException ame) {
            Dbg.user(new Msg("duplicate function argument", var_));
            return null;
        }

        return declType;
    }

}

